home *** CD-ROM | disk | FTP | other *** search
/ Software Vault: The Gold Collection / Software Vault - The Gold Collection (American Databankers) (1993).ISO / cdr47 / tsrsrc34.zip / MEMU.PAS < prev    next >
Pascal/Delphi Source File  |  1993-04-17  |  27KB  |  934 lines

  1. {**************************************************************************
  2. *   MEMU - utility unit for TSR Utilities.                                *
  3. *   Copyright (c) 1991 Kim Kokkonen, TurboPower Software.                 *
  4. *   May be freely distributed and used but not sold except by permission. *
  5. *                                                                         *
  6. *   Version 3.0 9/24/91                                                   *
  7. *     first release                                                       *
  8. *   Version 3.1 11/4/91                                                   *
  9. *     update for new WATCH identification behavior                        *
  10. *     update HasEnvironment for programs that shrink env size to 0        *
  11. *   Version 3.2 11/22/91                                                  *
  12. *     add FindHiMemStart function to generalize high memory access        *
  13. *     modify FindTheBlocks for new high memory approach                   *
  14. *     add MergeHiMemBlocks procedure to merge memory blocks in hi mem     *
  15. *     add ValidPsp function to determine whether a Psp still exists       *
  16. *   Version 3.3 1/8/92                                                    *
  17. *     add NextArg function to parse command lines more flexibly           *
  18. *   Version 3.4 2/14/92                                                   *
  19. *     change NextArg to ignore embedded '-'                               *
  20. *     change FindTheBlocks to support new /L switches in MAPMEM, DISABLE  *
  21. *     change StripNonAscii to allow European accented characters          *
  22. ***************************************************************************}
  23.  
  24. {$R-,S-,I-,V-,B-,F-,A-,E-,N-,G-,X-}
  25.  
  26. unit MemU;
  27.   {-Miscellaneous memory functions needed for TSR Utilities}
  28.  
  29. interface
  30.  
  31. const
  32.   {!!!!!! Following may change when WATCH reassembled. Check WATCH.MAP !!!!!}
  33.   ChangeVectors = $320;
  34.   OrigVectors = $720;
  35.  
  36.   {Offsets into resident copy of WATCH.COM for data storage}
  37.   WatchOfs = $80;             {Location of length of command line}
  38.   WatchOffset = $81;          {Location of start of command line}
  39.   NextChange = $104;          {Data structures within WATCH}
  40.   WatchId = 'TSR WATCHER';    {ID placed in WATCH command line}
  41.   MaxChanges = 128;           {Maximum number of vector changes stored in WATCH}
  42.  
  43.   Version = '3.4';            {TSR Utilities version number}
  44.   MarkID  = 'MM3.4 TSR';      {Marking string for TSR MARK}
  45.   FmarkID = 'FM3.4 TSR';      {Marking string for TSR file mark}
  46.   NmarkID = 'MN3.4 TSR';      {Marking string for TSR file mark}
  47.   NetMarkID = 'MN34';         {ID at start of net mark file}
  48.  
  49.   {Offsets into resident mark copies for id strings}
  50.   MarkOffset = $103;          {Where markID is found in MARK TSR}
  51.   FmarkOffset = $60;          {Where FmarkID is found in FMARK TSR}
  52.   NmarkOffset = $60;          {Where NmarkID is found in FMARK TSR}
  53.  
  54.   {Offsets into resident copy of MARK for data storage}
  55.   VectorOffset = $120;        {Where vector table is stored}
  56.   EGAsavOffset = $520;        {Where the EGA save save is stored}
  57.   IntComOffset = $528;        {Where the interapps comm area is stored}
  58.   ParentOffset = $538;        {(TER) Where parent's PSP segment is stored}
  59.   ParLenOffset = $53A;        {Where parent's PSP mcb length is stored}
  60.   EMScntOffset = $53C;        {Where count of EMS active pages is stored}
  61.   EMSmapOffset = $53E;        {Where the page map is stored}
  62.  
  63. const
  64.   MaxBlocks = 256;            {Max number of DOS allocation blocks supported}
  65.  
  66.   ProtectChar = '!';          {Marks whose name begins with this will be
  67.                                released ONLY if an exact name match occurs}
  68.  
  69. const
  70.   RBR = 0; {Receiver buffer register offset}
  71.   THR = 0; {Transmitter buffer register offset}
  72.   BRL = 0; {Baud rate low}
  73.   BRH = 1; {Baud rate high}
  74.   IER = 1; {Interrupt enable register}
  75.   IIR = 2; {Interrupt identification register}
  76.   LCR = 3; {Line control register}
  77.   MCR = 4; {Modem control register}
  78.   LSR = 5; {Line status register}
  79.   MSR = 6; {Modem status register}
  80.  
  81. type
  82.   OS =
  83.     record
  84.       O, S : Word;
  85.     end;
  86.  
  87.   StringPtr = ^String;
  88.  
  89.   NameArray = array[1..8] of Char;
  90.  
  91.   McbPtr = ^Mcb;
  92.   Mcb =
  93.     record
  94.       Id : Char;
  95.       Psp : Word;
  96.       Len : Word;
  97.       Unused : array[1..3] of Byte;
  98.       Name : NameArray;
  99.     end;
  100.  
  101.   Block =
  102.   record                      {Store info about each memory block}
  103.     mcb : Word;
  104.     psp : Word;
  105.     releaseIt : Boolean;
  106.   end;
  107.  
  108.   BlockType = 0..MaxBlocks;
  109.   BlockArray = array[1..MaxBlocks] of Block;
  110.  
  111.   McbGroup =
  112.   record
  113.     Count : Word;
  114.     Mcbs : array[1..MaxBlocks] of
  115.            record
  116.              mcb : Word;
  117.              psp : Word;
  118.            end;
  119.   end;
  120.  
  121.   ChangeBlock =
  122.   record                      {Store info about each vector takeover}
  123.     VecNum : byte;
  124.     case ID : byte of
  125.       0, 1 : (VecOfs, VecSeg : Word);
  126.       2    : (SaveCode : array[1..6] of byte);
  127.       $FF  : (PspAdd : Word);
  128.   end;
  129.   {
  130.   ID is interpreted as follows:
  131.     00 = ChangeBlock holds the new pointer for vector vecnum
  132.     01 = ChangeBlock holds pointer for vecnum but the block is disabled
  133.     02 = ChangeBlock holds the code underneath the vector patch
  134.     FF = ChangeBlock holds the segment of a new PSP
  135.   }
  136.   ChangeArray = array[0..MaxChanges] of ChangeBlock;
  137.  
  138.   {Structure of a device driver header}
  139.   DeviceHeader =
  140.     record
  141.       NextHeaderOffset : Word;    {Offset address of next device in chain}
  142.       NextHeaderSegment : Word;   {Segment address of next device in chain}
  143.       Attributes : Word;          {Device attributes}
  144.       StrategyEntPt : Word;       {Offset in current segment - strategy}
  145.       InterruptEntPt : Word;      {Offset in current segment - interrupt}
  146.       DeviceName : array[1..8] of Char; {Name of the device}
  147.     end;
  148.   DeviceHeaderPtr = ^DeviceHeader;
  149.   DeviceArray = array[1..256] of DeviceHeaderPtr;
  150.  
  151.   FileRec =
  152.     record
  153.       OpenCnt : Word;
  154.       OpenMode : Word;
  155.       Attribute : Byte;
  156.       Unknown1 : Word;
  157.       DCB : Pointer;
  158.       InitCluster : Word;
  159.       Time : Word;
  160.       Date : Word;
  161.       Size : LongInt;
  162.       Pos : LongInt;
  163.       BeginCluster : Word;
  164.       CurCluster : Word;
  165.       Block : Word;
  166.       Unknown2 : Byte;            {Varies with DOS version beyond here}
  167.       Name : array[0..7] of Char;
  168.       Ext : array[0..2] of Char;
  169.       Unknown3 : array[0..5] of Byte;
  170.       Owner : Word;
  171.       Unknown4 : Word;
  172.     end;
  173.  
  174.   SftRecPtr = ^SftRec;
  175.   SftRec =
  176.     record
  177.       Next : SftRecPtr;
  178.       Count : Word;
  179.       Files : array[1..255] of FileRec;
  180.     end;
  181.  
  182.   DosRec =
  183.     record
  184.       McbSeg : Word;
  185.       FirstDPB : Pointer;
  186.       FirstSFT : SftRecPtr;
  187.       ClockDriver : Pointer;
  188.       ConDriver : Pointer;
  189.       MaxBlockBytes : Word;
  190.       CachePtr : Pointer;
  191.       DriveTable : Pointer;
  192.       FcbTable : Pointer;
  193.       ProtectedFcbCount : Word;
  194.       BlockDevices : Byte;
  195.       LastDrive : Byte;
  196.       NullDevice : DeviceHeader;
  197.       JoinedDrives : Byte;           {Following valid DOS 4.0 or later}
  198.       SpecialProgOfs : Word;
  199.       IFSPtr : Pointer;
  200.       IFSList : Pointer;
  201.       BuffersX : Word;
  202.       BuffersY : Word;
  203.       BootDrive : Byte;
  204.       Unknown1 : Byte;
  205.       ExtMemSize : Word;
  206.     end;
  207.   DosRecPtr = ^DosRec;
  208.  
  209.   ComRec =  {State of the communications system}
  210.     record
  211.       Base : Word;
  212.       IERReg : Byte;
  213.       LCRReg : Byte;
  214.       MCRReg : Byte;
  215.       BRLReg : Byte;
  216.       BRHReg : Byte;
  217.     end;
  218.   ComArray = array[1..2] of ComRec;
  219.  
  220. const
  221.   Digits : array[0..$F] of Char = '0123456789ABCDEF';
  222.   DosDelimSet : set of Char = ['\', ':', #0];
  223.  
  224. var
  225.   DosVM : Byte;      {Minor DOS version number}
  226.   DosV : Byte;       {Major DOS version number}
  227.   DosVT : Word absolute DosVM; {Combined version number}
  228.   DosList : Pointer; {Pointer to DOS list of lists}
  229.   Mcb1 : McbPtr;     {First MCB in system}
  230.  
  231. function GetDosListPtr : Pointer;
  232.   {-Return address of DOS list of lists}
  233.  
  234. function GetUmbLinkStatus : Boolean;
  235.   {-Return status of DOS 5 upper memory block link}
  236.  
  237. function SetUmbLinkStatus(On : Boolean) : Word;
  238.   {-Change state of DOS 5 upper memory block link}
  239.  
  240. function DosVersion : Word;
  241.   {-Return DOS version number with high byte = major version number}
  242.  
  243. function TopOfMemSeg : Word;
  244.   {-Return segment of top of normal memory}
  245.  
  246. function FindHiMemStart : word;
  247.   {-Return segment of first mcb in high memory, 0 if none}
  248.  
  249. procedure MergeHiMemBlocks(HiMemSeg : Word);
  250.   {-Merge adjacent blocks in high memory, starting with HiMemSeg}
  251.  
  252. function HexB(B : Byte) : String;
  253.   {-Return hex string for byte}
  254.  
  255. function HexW(W : Word) : String;
  256.   {-Return hex string for word}
  257.  
  258. function HexPtr(P : Pointer) : string;
  259.   {-Return hex string for pointer}
  260.  
  261. function StUpcase(S : String) : String;
  262.   {-Return the uppercase string}
  263.  
  264. function JustFilename(PathName : String) : String;
  265.   {-Return just the filename of a pathname}
  266.  
  267. function JustName(PathName : String) : String;
  268.   {-Return just the name (no extension, no path) of a pathname}
  269.  
  270. function Extend(S : String; Len : Byte) : String;
  271.   {-Truncate or pad S to length Len}
  272.  
  273. function SmartExtend(S : String; Len : Byte) : String;
  274.   {-Truncate or pad S to length Len; end with '...' if truncated}
  275.  
  276. function Asc2Str(Name : NameArray) : String;
  277.   {-Convert array[1..8] of char to string}
  278.  
  279. procedure StripNonAscii(var S : String);
  280.   {-Return an empty string if input contains non-ASCII characters}
  281.  
  282. function CommaIze(L : LongInt; Width : Byte) : String;
  283.   {-Convert L to a string and add commas for thousands}
  284.  
  285. function HasEnvironment(HiMemSeg : Word; M : McbPtr) : Boolean;
  286.   {-Return True if M has an associated environment block}
  287.  
  288. function ValidPsp(HiMemSeg, PspSeg, PspLen : Word) : Boolean;
  289.   {-Return True if PspSeg is a valid, existing Psp}
  290.  
  291. function NameFromEnv(M : McbPtr) : String;
  292.   {-Return M's name from its environment (already known to exist)}
  293.  
  294. function NameFromMcb(M : McbPtr) : String;
  295.   {-Return name from the Mcb (DOS 4+ only)}
  296.  
  297. function MasterCommandSeg : Word;
  298.   {-Return PSP segment of master COMMAND.COM}
  299.  
  300. function WatchPspSeg : Word;
  301.   {-Find copy of WATCH.COM in memory, returning its PSP segment or 0}
  302.  
  303. procedure FindTheBlocks(UseLoMem : Boolean;
  304.                         HiMemSeg : Word;
  305.                         var Blocks : BlockArray;
  306.                         var BlockMax : BlockType;
  307.                         var StartMcb : Word;
  308.                         var CommandSeg : Word);
  309.   {-Scan memory for the allocated memory blocks}
  310.  
  311. procedure StuffKey(W : Word);
  312.   {-Stuff one key into the keyboard buffer}
  313.  
  314. procedure StuffKeys(Keys : string; ClearFirst : Boolean);
  315.   {-Stuff up to 16 keys into keyboard buffer}
  316.  
  317. function ExistFile(path : String) : Boolean;
  318.   {-Return true if file exists}
  319.  
  320. function NextArg(S : String; var SPos : Word) : String;
  321.   {-Return next argument beginning at SPos in S.
  322.     Increment SPos to point past the argument.
  323.     Arguments are delimited by white space, and '/'.}
  324.  
  325. procedure IntsOff;
  326.   {-Turn off CPU interrupts}
  327. inline($FA);
  328.  
  329. procedure IntsOn;
  330.   {-Turn on CPU interrupts}
  331. inline($FB);
  332.  
  333. procedure NullJump;
  334.   {-Slight delay}
  335. inline($EB/$00);
  336.  
  337.   {=======================================================================}
  338.  
  339. implementation
  340.  
  341. uses
  342.   xms;
  343.  
  344.   function GetDosListPtr : Pointer; Assembler;
  345.     {-Return address of DOS list of lists}
  346.   asm
  347.     mov     ah,$52
  348.     int     $21
  349.     mov     dx,es
  350.     mov     ax,bx
  351.   end;
  352.  
  353.   function GetUmbLinkStatus : Boolean; Assembler;
  354.     {-Return status of DOS 5 upper memory block link}
  355.   asm
  356.     mov     ax,$5802
  357.     int     $21
  358.   end;
  359.  
  360.   function SetUmbLinkStatus(On : Boolean) : Word; Assembler;
  361.     {-Change state of DOS 5 upper memory block link}
  362.   asm
  363.     mov     ax,$5803
  364.     mov     bl,On
  365.     xor     bh,bh
  366.     int     $21
  367.     jc      @1
  368.     xor     ax,ax
  369. @1:
  370.   end;
  371.  
  372.   function DosVersion : Word; Assembler;
  373.     {-Return major DOS version number}
  374.   asm
  375.     mov     ah,$30
  376.     int     $21
  377.     xchg    ah,al
  378.   end;
  379.  
  380.   function TopOfMemSeg : Word;
  381.     {-Return segment of top of memory}
  382.   var
  383.     KBRAM : Word;
  384.   begin
  385.     asm
  386.       int $12
  387.       mov KBRAM,ax
  388.     end;
  389.     TopOfMemSeg := KBRAM shl 6;
  390.   end;
  391.  
  392.   function FindHiMemStart : word;
  393.     {-Return segment of first mcb in high memory}
  394.   var
  395.     Segment : word;
  396.     Size : word;
  397.     Mseg : word;
  398.     M : mcbptr;
  399.     N : mcbptr;
  400.     Status : byte;
  401.     Done : boolean;
  402.     Invalid : boolean;
  403.   begin
  404.     {assume failure}
  405.     FindHiMemStart := 0;
  406.  
  407.     {assure XMS driver installed}
  408.     if not XmsInstalled then
  409.       Exit;
  410.  
  411.     {look for umbs}
  412.     Status := AllocateUmbMem($FFFF, Segment, Size);
  413.     case status of
  414.       $B0, $B1 : ; {UMBs are possible, but not to allocate $FFFF paragraphs}
  415.     else
  416.       Exit;        {UMBs are not possible}
  417.     end;
  418.  
  419.     {find the starting umb}
  420.     Mseg := TopOfMemSeg;
  421.     Done := False;
  422.     repeat
  423.       M := Ptr(Mseg, 0);
  424.       case M^.Id of
  425.         'M' {, 'Z'} : {There must be at least 2 mcbs in high memory}
  426.           begin
  427.             {determine whether this is a valid chain of mcbs}
  428.             N := M;
  429.             Invalid := False;
  430.             repeat
  431.               case N^.Id of
  432.                 'M' :
  433.                   if $FFFE-N^.Len >= OS(N).S then
  434.                     {next mcb won't land beyond $FFFF}
  435.                     N := Ptr(OS(N).S+N^.Len+1, 0)
  436.                   else
  437.                     Invalid := true;
  438.                 'Z' :
  439.                   begin
  440.                     {found end of chain starting at M}
  441.                     FindHiMemStart := Mseg;
  442.                     Done := True;
  443.                   end;
  444.               else
  445.                 Invalid := True;
  446.               end;
  447.             until Done or Invalid;
  448.           end;
  449.       end;
  450.       if Mseg < $FFFF then
  451.         inc(Mseg)
  452.       else
  453.         Done := True;
  454.     until Done;
  455.   end;
  456.  
  457.   procedure MergeHiMemBlocks(HiMemSeg : Word);
  458.     {-Merge adjacent blocks in high memory, starting with HiMemSeg}
  459.   var
  460.     M : McbPtr;
  461.     N : McbPtr;
  462.     Done : Boolean;
  463.   begin
  464.     if HiMemSeg = 0 then
  465.       Exit;
  466.     M := Ptr(HiMemSeg, 0);
  467.     Done := False;
  468.     repeat
  469.       Done := (M^.Id = 'Z');
  470.       if not Done then begin
  471.         N := Ptr(OS(M).S+M^.Len+1, 0);
  472.         if (M^.Psp = 0) and (N^.Psp = 0) then begin
  473.           {This block and the next are both free}
  474.           inc(M^.Len, N^.Len+1);
  475.           M^.Id := N^.Id;
  476.         end else
  477.           M := N;
  478.       end;
  479.     until Done;
  480.   end;
  481.  
  482.   function HexB(B : Byte) : String;
  483.     {-Return hex string for byte}
  484.   begin
  485.     HexB[0] := #2;
  486.     HexB[1] := Digits[B shr 4];
  487.     HexB[2] := Digits[B and $F];
  488.   end;
  489.  
  490.   function HexW(W : Word) : String;
  491.     {-Return hex string for word}
  492.   begin
  493.     HexW[0] := #4;
  494.     HexW[1] := Digits[Hi(W) shr 4];
  495.     HexW[2] := Digits[Hi(W) and $F];
  496.     HexW[3] := Digits[Lo(W) shr 4];
  497.     HexW[4] := Digits[Lo(W) and $F];
  498.   end;
  499.  
  500.   function HexPtr(P : Pointer) : string;
  501.     {-Return hex string for pointer}
  502.   begin
  503.     HexPtr := HexW(OS(P).S)+':'+HexW(OS(P).O);
  504.   end;
  505.  
  506.   function StUpcase(s : String) : String;
  507.     {-Return the uppercase string}
  508.   var
  509.     i : Byte;
  510.   begin
  511.     for i := 1 to Length(s) do
  512.       s[i] := UpCase(s[i]);
  513.     StUpcase := s;
  514.   end;
  515.  
  516.   function JustFilename(PathName : String) : String;
  517.     {-Return just the filename of a pathname}
  518.   var
  519.     I : Word;
  520.   begin
  521.     I := Word(Length(PathName))+1;
  522.     repeat
  523.       Dec(I);
  524.     until (PathName[I] in DosDelimSet) or (I = 0);
  525.     JustFilename := Copy(PathName, I+1, 64);
  526.   end;
  527.  
  528.   function JustName(PathName : String) : String;
  529.     {-Return just the name (no extension, no path) of a pathname}
  530.   var
  531.     DotPos : Byte;
  532.   begin
  533.     PathName := JustFilename(PathName);
  534.     DotPos := Pos('.', PathName);
  535.     if DotPos > 0 then
  536.       PathName := Copy(PathName, 1, DotPos-1);
  537.     JustName := PathName;
  538.   end;
  539.  
  540.   function Extend(S : String; Len : Byte) : String;
  541.     {-Truncate or pad S to length Len}
  542.   begin
  543.     if Length(S) < Len then
  544.       FillChar(S[Length(S)+1], Len-Length(S), ' ');
  545.     S[0] := Char(Len);
  546.     Extend := S;
  547.   end;
  548.  
  549.   function SmartExtend(S : String; Len : Byte) : String;
  550.     {-Truncate or pad S to length Len; end with '...' if truncated}
  551.   begin
  552.     if Length(S) > Len then
  553.       SmartExtend := copy(S, 1, Len-3)+'...'
  554.     else
  555.       SmartExtend := Extend(S, Len);
  556.   end;
  557.  
  558.   function Asc2Str(Name : NameArray) : String;
  559.     {-Convert array[1..8] of char to string}
  560.   var
  561.     I : Integer;
  562.   begin
  563.     I := 1;
  564.     while (I <= 8) and (Name[I] <> #0) and (Name[I] <> ' ') do begin
  565.       Asc2Str[I] := Name[I];
  566.       Inc(I);
  567.     end;
  568.     Asc2Str[0] := Char(I-1);
  569.   end;
  570.  
  571.   procedure StripNonAscii(var S : String);
  572.     {-Return an empty string if input contains non-ASCII characters}
  573.   var
  574.     I : Integer;
  575.     Ok : Boolean;
  576.   begin
  577.     Ok := True;
  578.     I := 1;
  579.     while Ok and (I <= Length(S)) do begin
  580.       case S[I] of
  581.         #0..#31, #127, #166..#255 : Ok := False;
  582.       end;
  583.       Inc(I);
  584.     end;
  585.     if not Ok then
  586.       S := '';
  587.   end;
  588.  
  589.   function CommaIze(L : LongInt; Width : Byte) : String;
  590.     {-Convert L to a string and add commas for thousands}
  591.   var
  592.     I : Word;
  593.     Len : Word;
  594.     S : String[19];
  595.   begin
  596.     Str(L, S);
  597.     Len := Length(S);
  598.     I := Len;
  599.     while I > 1 do begin
  600.       if (Len+1-I) mod 3 = 0 then
  601.         insert(',', S, I);
  602.       dec(I);
  603.     end;
  604.     while Length(S) < Width do
  605.       insert(' ', S, 1);
  606.     CommaIze := S;
  607.   end;
  608.  
  609.   function HasEnvironment(HiMemSeg : Word; M : McbPtr) : Boolean;
  610.     {-Return True if M has an associated environment block}
  611.   var
  612.     EnvSeg : Word;
  613.  
  614.     function HasEnv(Start : McbPtr) : Boolean;
  615.     var
  616.       N : McbPtr;
  617.       Done : Boolean;
  618.     begin
  619.       N := Start;
  620.       repeat
  621.         if (N^.Psp = M^.Psp) and (N^.Len > 0) and (EnvSeg = OS(N).S+1) then begin
  622.           HasEnv := True;
  623.           Exit;
  624.         end;
  625.         Done := (N^.Id = 'Z');
  626.         N := Ptr(OS(N).S+N^.Len+1, 0);
  627.       until Done;
  628.       HasEnv := False;
  629.     end;
  630.  
  631.   begin
  632.     EnvSeg := MemW[M^.Psp:$2C];
  633.     if HasEnv(Mcb1) then
  634.       HasEnvironment := True
  635.     else if (HiMemSeg <> 0) and HasEnv(Ptr(HiMemSeg, 0)) then
  636.       HasEnvironment := True
  637.     else
  638.       HasEnvironment := False;
  639.   end;
  640.  
  641.   function ValidPsp(HiMemSeg, PspSeg, PspLen : Word) : Boolean;
  642.     {-Return True if PspSeg is a valid, existing Psp}
  643.  
  644.     function ValidP(Start : McbPtr) : Boolean;
  645.     var
  646.       N : McbPtr;
  647.       Done : Boolean;
  648.     begin
  649.       N := Start;
  650.       repeat
  651.         if (N^.Psp = PspSeg) and (N^.Len = PspLen) then begin
  652.           ValidP := True;
  653.           Exit;
  654.         end;
  655.         Done := (N^.Id = 'Z');
  656.         N := Ptr(OS(N).S+N^.Len+1, 0);
  657.       until Done;
  658.       ValidP := False;
  659.     end;
  660.  
  661.   begin
  662.     if ValidP(Mcb1) then
  663.       ValidPsp := True
  664.     else if (HiMemSeg <> 0) and ValidP(Ptr(HiMemSeg, 0)) then
  665.       ValidPsp := True
  666.     else
  667.       ValidPsp := False;
  668.   end;
  669.  
  670.   function NameFromEnv(M : McbPtr) : String;
  671.     {-Return M's name from its environment (already known to exist)}
  672.   type
  673.     CharArray = array[0..32767] of Char;
  674.     CharArrayPtr = ^CharArray;
  675.   var
  676.     E : Word;
  677.     Eptr : CharArrayPtr;
  678.     Name : String[79];
  679.     Nlen : Byte absolute Name;
  680.   begin
  681.     Eptr := Ptr(MemW[M^.Psp:$2C], 0);
  682.     E := 0;
  683.     repeat
  684.       if Eptr^[E] = #0 then begin
  685.         Inc(E);
  686.         if Eptr^[E] = #0 then begin
  687.           {found end of environment}
  688.           Inc(E, 3);
  689.           Nlen := 0;
  690.           while (Nlen < 63) and (Eptr^[E] <> #0) do begin
  691.             Inc(Nlen);
  692.             Name[Nlen] := Eptr^[E];
  693.             Inc(E);
  694.           end;
  695.           StripNonAscii(Name);
  696.           NameFromEnv := JustName(Name);
  697.           Exit;
  698.         end;
  699.       end;
  700.       Inc(E);
  701.     until (E > 32767);
  702.     NameFromEnv := '';
  703.   end;
  704.  
  705.   function NameFromMcb(M : McbPtr) : String;
  706.     {-Return name from the Mcb (DOS 4+ only)}
  707.   var
  708.     Name : String[79];
  709.   begin
  710.     Name := Asc2Str(M^.Name);
  711.     StripNonAscii(Name);
  712.     NameFromMcb := Name;
  713.   end;
  714.  
  715.   function MasterCommandSeg : Word;
  716.     {-Return PSP segment of master COMMAND.COM}
  717.   var
  718.     curmcb : mcbptr;
  719.     mseg : word;
  720.     par : word;
  721.   begin
  722.     {First block}
  723.     curmcb := mcb1;
  724.     repeat
  725.       curmcb := ptr(OS(curmcb).s+curmcb^.len+1, 0);
  726.       par := memw[curmcb^.psp:$16];
  727.       mseg := OS(curmcb).s;
  728.       if (par = curmcb^.psp) and (mseg+1 = curmcb^.psp) then begin
  729.         MasterCommandSeg := curmcb^.psp;
  730.         exit;
  731.       end;
  732.     until curmcb^.id = 'Z';
  733.     MasterCommandSeg := 0;
  734.   end;
  735.  
  736.   function WatchPspSeg : Word; assembler;
  737.     {-Find copy of WATCH.COM in memory, returning its PSP segment or zero}
  738.   asm
  739.     mov ax,$7761     {id call to WATCH}
  740.     int $21
  741.     jc @1
  742.     cmp ax,$6177     {WATCH flips ah and al if installed}
  743.     jne @1
  744.     mov ax,bx        {WATCH returns its own CS in BX}
  745.     jmp @2
  746. @1: xor ax,ax        {not installed}
  747. @2:
  748.   end;
  749.  
  750.   procedure FindTheBlocks(UseLoMem : Boolean;
  751.                           HiMemSeg : Word;
  752.                           var Blocks : BlockArray;
  753.                           var BlockMax : BlockType;
  754.                           var StartMcb : Word;
  755.                           var CommandSeg : Word);
  756.     {-Scan memory for the allocated memory blocks}
  757.   const
  758.     MidBlockID = $4D;         {Byte DOS uses to identify part of MCB chain}
  759.     EndBlockID = $5A;         {Byte DOS uses to identify last block of MCB chain}
  760.   var
  761.     mcbSeg : Word;            {Segment address of current MCB}
  762.     nextSeg : Word;           {Computed segment address for the next MCB}
  763.     gotFirst : Boolean;       {True after first MCB is found}
  764.     gotLast : Boolean;        {True after last MCB is found}
  765.     idbyte : Byte;            {Byte that DOS uses to identify an MCB}
  766.  
  767.     procedure StoreTheBlock(SaveBlock : Boolean;
  768.                             var mcbSeg, nextSeg : Word;
  769.                             var gotFirst, gotLast : Boolean);
  770.       {-Store information regarding the memory block}
  771.     var
  772.       nextID : Byte;
  773.       PspAdd : Word;       {Segment address of the current PSP}
  774.       mcbLen : Word;       {Size of the current memory block in paragraphs}
  775.  
  776.     begin
  777.  
  778.       PspAdd := MemW[mcbSeg:1]; {Address of program segment prefix for MCB}
  779.       mcbLen := MemW[mcbSeg:3]; {Size of the MCB in paragraphs}
  780.       nextSeg := Succ(mcbSeg+mcbLen); {Where the next MCB should be}
  781.       nextID := Mem[nextSeg:0];
  782.  
  783.       if gotLast or (nextID = EndBlockID) or (nextID = MidBlockID) then begin
  784.         if BlockMax < MaxBlocks then begin
  785.           gotFirst := True;
  786.           if SaveBlock then begin
  787.             inc(BlockMax);
  788.             with Blocks[BlockMax] do begin
  789.               mcb := mcbSeg;
  790.               psp := PspAdd;
  791.             end;
  792.           end;
  793.         end;
  794.         {Store master COMMAND.COM segment}
  795.         if CommandSeg = 0 then
  796.           if (McbSeg+1 = PspAdd) and (MemW[PspAdd:$16] = PspAdd) then
  797.             CommandSeg := PspAdd;
  798.       end;
  799.     end;
  800.  
  801.     procedure ScanBlocks(SaveBlock : Boolean);
  802.       {-Scan memory until ending block is found}
  803.     begin
  804.       repeat
  805.         idbyte := Mem[mcbSeg:0];
  806.         if idbyte = MidBlockID then begin
  807.           StoreTheBlock(SaveBlock, mcbSeg, nextSeg, gotFirst, gotLast);
  808.           if gotFirst then
  809.             mcbSeg := nextSeg
  810.           else
  811.             inc(mcbSeg);
  812.         end else if gotFirst and (idbyte = EndBlockID) then begin
  813.           gotLast := True;
  814.           StoreTheBlock(SaveBlock, mcbSeg, nextSeg, gotFirst, gotLast);
  815.         end else
  816.           {Start block was invalid}
  817.           gotLast := True;
  818.       until gotLast;
  819.     end;
  820.  
  821.   begin
  822.     BlockMax := 0;
  823.     CommandSeg := 0;
  824.     StartMCB := OS(MCB1).S;
  825.  
  826.     mcbSeg := StartMCB;
  827.     gotFirst := False;
  828.     gotLast := False;
  829.     ScanBlocks(UseLoMem);
  830.  
  831.     if HiMemSeg <> 0 then begin
  832.       mcbSeg := HiMemSeg;
  833.       gotFirst := False;
  834.       gotLast := False;
  835.       ScanBlocks(True);
  836.     end;
  837.   end;
  838.  
  839.   const
  840.     KbdStart = $1E;
  841.     KbdEnd = $3C;
  842.   var
  843.     KbdHead : Word absolute $40 : $1A;
  844.     KbdTail : Word absolute $40 : $1C;
  845.  
  846.   procedure StuffKey(W : Word);
  847.     {-Stuff one key into the keyboard buffer}
  848.   var
  849.     SaveKbdTail : Word;
  850.   begin
  851.     SaveKbdTail := KbdTail;
  852.     if KbdTail = KbdEnd then
  853.       KbdTail := KbdStart
  854.     else
  855.       Inc(KbdTail, 2);
  856.     if KbdTail = KbdHead then
  857.       KbdTail := SaveKbdTail
  858.     else
  859.       MemW[$40:SaveKbdTail] := W;
  860.   end;
  861.  
  862.   procedure StuffKeys(Keys : string; ClearFirst : Boolean);
  863.     {-Stuff up to 16 keys into keyboard buffer}
  864.   var
  865.     Len : Byte;
  866.     I : Byte;
  867.   begin
  868.     if ClearFirst then
  869.       KbdTail := KbdHead;
  870.     Len := Length(Keys);
  871.     if Len > 16 then
  872.       Len := 16;
  873.     for I := 1 to Length(Keys) do
  874.       StuffKey(Ord(Keys[I]));
  875.   end;
  876.  
  877.   function ExistFile(path : String) : Boolean;
  878.     {-Return true if file exists}
  879.   var
  880.     F : file;
  881.   begin
  882.     Assign(F, path);
  883.     Reset(F);
  884.     if IoResult = 0 then begin
  885.       ExistFile := True;
  886.       Close(F);
  887.     end else
  888.       ExistFile := False;
  889.   end;
  890.  
  891.   function NextArg(S : String; var SPos : Word) : String;
  892.     {-Return next argument beginning at SPos in S.
  893.       Increment SPos to point past the argument.
  894.       Arguments are delimited by white space and '/'}
  895.   var
  896.     Start : Word;
  897.  
  898.     function Delimiter(Ch : Char) : Boolean;
  899.     begin
  900.       case Ch of
  901.         #0..' ', '/' : Delimiter := True;
  902.       else
  903.         Delimiter := False;
  904.       end;
  905.     end;
  906.  
  907.   begin
  908.     {Skip leading white space}
  909.     while (SPos <= Length(S)) and (S[SPos] <= ' ') do
  910.       inc(SPos);
  911.  
  912.     {Exit if beyond end of string}
  913.     if SPos > Length(S) then begin
  914.       NextArg := '';
  915.       Exit;
  916.     end;
  917.  
  918.     {Find end of this argument}
  919.     Start := SPos;
  920.     inc(SPos);
  921.     while (SPos <= Length(S)) and not Delimiter(S[Spos]) do
  922.       inc(SPos);
  923.  
  924.     {Return the string}
  925.     NextArg := Copy(S, Start, SPos-Start);
  926.   end;
  927.  
  928. begin
  929.   DosVT := DosVersion;
  930.   DosList := GetDosListPtr;     {pointer to dos list of lists}
  931.   Mcb1 := Ptr(MemW[OS(DosList).S:OS(DosList).O-2], 0); {first Mcb}
  932. end.
  933.  
  934.